// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/tracing/background_tracing_rule.h"

#include <string>

#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/statistics_recorder.h"
#include "base/rand_util.h"
#include "base/strings/safe_sprintf.h"
#include "base/values.h"
#include "components/tracing/common/tracing_messages.h"
#include "content/browser/tracing/background_tracing_manager_impl.h"
#include "content/browser/tracing/trace_message_filter.h"
#include "content/public/browser/browser_thread.h"

namespace {

const char kConfigRuleKey[] = "rule";
const char kConfigCategoryKey[] = "category";
const char kConfigRuleTriggerNameKey[] = "trigger_name";
const char kConfigRuleTriggerDelay[] = "trigger_delay";
const char kConfigRuleTriggerChance[] = "trigger_chance";
const char kConfigRuleStopTracingOnRepeatedReactive[] = "stop_tracing_on_repeated_reactive";

const char kConfigRuleHistogramNameKey[] = "histogram_name";
const char kConfigRuleHistogramValueOldKey[] = "histogram_value";
const char kConfigRuleHistogramValue1Key[] = "histogram_lower_value";
const char kConfigRuleHistogramValue2Key[] = "histogram_upper_value";
const char kConfigRuleHistogramRepeatKey[] = "histogram_repeat";

const char kConfigRuleRandomIntervalTimeoutMin[] = "timeout_min";
const char kConfigRuleRandomIntervalTimeoutMax[] = "timeout_max";

const char kConfigRuleTypeMonitorNamed[] = "MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED";

const char kConfigRuleTypeMonitorHistogram[] = "MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE";

const char kConfigRuleTypeTraceOnNavigationUntilTriggerOrFull[] = "TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL";

const char kConfigRuleTypeTraceAtRandomIntervals[] = "TRACE_AT_RANDOM_INTERVALS";

const char kTraceAtRandomIntervalsEventName[] = "ReactiveTraceAtRandomIntervals";

const int kConfigTypeNavigationTimeout = 30;
const int kReactiveTraceRandomStartTimeMin = 60;
const int kReactiveTraceRandomStartTimeMax = 120;

} // namespace

namespace content {

BackgroundTracingRule::BackgroundTracingRule()
    : trigger_chance_(1.0)
    , trigger_delay_(-1)
    , stop_tracing_on_repeated_reactive_(false)
    , category_preset_(BackgroundTracingConfigImpl::CATEGORY_PRESET_UNSET)
{
}

BackgroundTracingRule::BackgroundTracingRule(int trigger_delay)
    : trigger_chance_(1.0)
    , trigger_delay_(trigger_delay)
    , stop_tracing_on_repeated_reactive_(false)
    , category_preset_(BackgroundTracingConfigImpl::CATEGORY_PRESET_UNSET)
{
}

BackgroundTracingRule::~BackgroundTracingRule() { }

bool BackgroundTracingRule::ShouldTriggerNamedEvent(
    const std::string& named_event) const
{
    return false;
}

int BackgroundTracingRule::GetTraceDelay() const
{
    return trigger_delay_;
}

void BackgroundTracingRule::IntoDict(base::DictionaryValue* dict) const
{
    DCHECK(dict);
    if (trigger_chance_ < 1.0)
        dict->SetDouble(kConfigRuleTriggerChance, trigger_chance_);

    if (trigger_delay_ != -1)
        dict->SetInteger(kConfigRuleTriggerDelay, trigger_delay_);

    if (stop_tracing_on_repeated_reactive_) {
        dict->SetBoolean(kConfigRuleStopTracingOnRepeatedReactive,
            stop_tracing_on_repeated_reactive_);
    }

    if (category_preset_ != BackgroundTracingConfigImpl::CATEGORY_PRESET_UNSET) {
        dict->SetString(
            kConfigCategoryKey,
            BackgroundTracingConfigImpl::CategoryPresetToString(category_preset_));
    }
}

void BackgroundTracingRule::Setup(const base::DictionaryValue* dict)
{
    dict->GetDouble(kConfigRuleTriggerChance, &trigger_chance_);
    dict->GetInteger(kConfigRuleTriggerDelay, &trigger_delay_);
    dict->GetBoolean(kConfigRuleStopTracingOnRepeatedReactive,
        &stop_tracing_on_repeated_reactive_);
}

namespace {

    class NamedTriggerRule : public BackgroundTracingRule {
    private:
        NamedTriggerRule(const std::string& named_event)
            : named_event_(named_event)
        {
        }

    public:
        static std::unique_ptr<BackgroundTracingRule> Create(
            const base::DictionaryValue* dict)
        {
            std::string trigger_name;
            if (!dict->GetString(kConfigRuleTriggerNameKey, &trigger_name))
                return nullptr;

            return std::unique_ptr<BackgroundTracingRule>(
                new NamedTriggerRule(trigger_name));
        }

        void IntoDict(base::DictionaryValue* dict) const override
        {
            DCHECK(dict);
            BackgroundTracingRule::IntoDict(dict);
            dict->SetString(kConfigRuleKey, kConfigRuleTypeMonitorNamed);
            dict->SetString(kConfigRuleTriggerNameKey, named_event_.c_str());
        }

        bool ShouldTriggerNamedEvent(const std::string& named_event) const override
        {
            return named_event == named_event_;
        }

    private:
        std::string named_event_;
    };

    class HistogramRule : public BackgroundTracingRule,
                          public TracingControllerImpl::TraceMessageFilterObserver {
    private:
        HistogramRule(const std::string& histogram_name,
            int histogram_lower_value,
            int histogram_upper_value,
            bool repeat)
            : histogram_name_(histogram_name)
            , histogram_lower_value_(histogram_lower_value)
            , histogram_upper_value_(histogram_upper_value)
            , repeat_(repeat)
        {
        }

    public:
        static std::unique_ptr<BackgroundTracingRule> Create(
            const base::DictionaryValue* dict)
        {
            std::string histogram_name;
            if (!dict->GetString(kConfigRuleHistogramNameKey, &histogram_name))
                return nullptr;

            // Optional parameter, so we don't need to check if the key exists.
            bool repeat = true;
            dict->GetBoolean(kConfigRuleHistogramRepeatKey, &repeat);

            int histogram_lower_value;
            int histogram_upper_value = std::numeric_limits<int>::max();

            if (!dict->GetInteger(kConfigRuleHistogramValue1Key,
                    &histogram_lower_value)) {
                // Check for the old naming.
                if (!dict->GetInteger(kConfigRuleHistogramValueOldKey,
                        &histogram_lower_value))
                    return nullptr;
            }

            dict->GetInteger(kConfigRuleHistogramValue2Key, &histogram_upper_value);

            if (histogram_lower_value >= histogram_upper_value)
                return nullptr;

            return std::unique_ptr<BackgroundTracingRule>(new HistogramRule(
                histogram_name, histogram_lower_value, histogram_upper_value, repeat));
        }

        ~HistogramRule() override
        {
            base::StatisticsRecorder::ClearCallback(histogram_name_);
            TracingControllerImpl::GetInstance()->RemoveTraceMessageFilterObserver(
                this);
        }

        // BackgroundTracingRule implementation
        void Install() override
        {
            base::StatisticsRecorder::SetCallback(
                histogram_name_,
                base::Bind(&HistogramRule::OnHistogramChangedCallback,
                    base::Unretained(this), histogram_name_,
                    histogram_lower_value_, histogram_upper_value_, repeat_));

            TracingControllerImpl::GetInstance()->AddTraceMessageFilterObserver(this);
        }

        void IntoDict(base::DictionaryValue* dict) const override
        {
            DCHECK(dict);
            BackgroundTracingRule::IntoDict(dict);
            dict->SetString(kConfigRuleKey, kConfigRuleTypeMonitorHistogram);
            dict->SetString(kConfigRuleHistogramNameKey, histogram_name_.c_str());
            dict->SetInteger(kConfigRuleHistogramValue1Key, histogram_lower_value_);
            dict->SetInteger(kConfigRuleHistogramValue2Key, histogram_upper_value_);
            dict->SetBoolean(kConfigRuleHistogramRepeatKey, repeat_);
        }

        void OnHistogramTrigger(const std::string& histogram_name) const override
        {
            if (histogram_name != histogram_name_)
                return;

            content::BrowserThread::PostTask(
                content::BrowserThread::UI, FROM_HERE,
                base::Bind(
                    &BackgroundTracingManagerImpl::OnRuleTriggered,
                    base::Unretained(BackgroundTracingManagerImpl::GetInstance()), this,
                    BackgroundTracingManager::StartedFinalizingCallback()));
        }

        void AbortTracing()
        {
            content::BrowserThread::PostTask(
                content::BrowserThread::UI, FROM_HERE,
                base::Bind(
                    &BackgroundTracingManagerImpl::AbortScenario,
                    base::Unretained(BackgroundTracingManagerImpl::GetInstance())));
        }

        // TracingControllerImpl::TraceMessageFilterObserver implementation
        void OnTraceMessageFilterAdded(TraceMessageFilter* filter) override
        {
            filter->Send(
                new TracingMsg_SetUMACallback(histogram_name_, histogram_lower_value_,
                    histogram_upper_value_, repeat_));
        }

        void OnTraceMessageFilterRemoved(TraceMessageFilter* filter) override
        {
            filter->Send(new TracingMsg_ClearUMACallback(histogram_name_));
        }

        void OnHistogramChangedCallback(const std::string& histogram_name,
            base::Histogram::Sample reference_lower_value,
            base::Histogram::Sample reference_upper_value,
            bool repeat,
            base::Histogram::Sample actual_value)
        {
            if (reference_lower_value > actual_value || reference_upper_value < actual_value) {
                if (!repeat)
                    AbortTracing();
                return;
            }

            OnHistogramTrigger(histogram_name);
        }

        bool ShouldTriggerNamedEvent(const std::string& named_event) const override
        {
            return named_event == histogram_name_;
        }

    private:
        std::string histogram_name_;
        int histogram_lower_value_;
        int histogram_upper_value_;
        bool repeat_;
    };

    class TraceForNSOrTriggerOrFullRule : public BackgroundTracingRule {
    private:
        TraceForNSOrTriggerOrFullRule(const std::string& named_event)
            : BackgroundTracingRule(kConfigTypeNavigationTimeout)
            , named_event_(named_event)
        {
        }

    public:
        static std::unique_ptr<BackgroundTracingRule> Create(
            const base::DictionaryValue* dict)
        {
            std::string trigger_name;
            if (!dict->GetString(kConfigRuleTriggerNameKey, &trigger_name))
                return nullptr;

            return std::unique_ptr<BackgroundTracingRule>(
                new TraceForNSOrTriggerOrFullRule(trigger_name));
        }

        // BackgroundTracingRule implementation
        void IntoDict(base::DictionaryValue* dict) const override
        {
            DCHECK(dict);
            BackgroundTracingRule::IntoDict(dict);
            dict->SetString(kConfigRuleKey,
                kConfigRuleTypeTraceOnNavigationUntilTriggerOrFull);
            dict->SetString(kConfigRuleTriggerNameKey, named_event_.c_str());
        }

        bool ShouldTriggerNamedEvent(const std::string& named_event) const override
        {
            return named_event == named_event_;
        }

    private:
        std::string named_event_;
    };

    class TraceAtRandomIntervalsRule : public BackgroundTracingRule {
    private:
        TraceAtRandomIntervalsRule(int timeout_min, int timeout_max)
            : timeout_min_(timeout_min)
            , timeout_max_(timeout_max)
        {
            named_event_ = GenerateUniqueName();
        }

    public:
        static std::unique_ptr<BackgroundTracingRule> Create(
            const base::DictionaryValue* dict)
        {
            int timeout_min;
            if (!dict->GetInteger(kConfigRuleRandomIntervalTimeoutMin, &timeout_min))
                return nullptr;

            int timeout_max;
            if (!dict->GetInteger(kConfigRuleRandomIntervalTimeoutMax, &timeout_max))
                return nullptr;

            if (timeout_min > timeout_max)
                return nullptr;

            return std::unique_ptr<BackgroundTracingRule>(
                new TraceAtRandomIntervalsRule(timeout_min, timeout_max));
        }
        ~TraceAtRandomIntervalsRule() override { }

        void IntoDict(base::DictionaryValue* dict) const override
        {
            DCHECK(dict);
            BackgroundTracingRule::IntoDict(dict);
            dict->SetString(kConfigRuleKey, kConfigRuleTypeTraceAtRandomIntervals);
            dict->SetInteger(kConfigRuleRandomIntervalTimeoutMin, timeout_min_);
            dict->SetInteger(kConfigRuleRandomIntervalTimeoutMax, timeout_max_);
        }

        void Install() override
        {
            handle_ = BackgroundTracingManagerImpl::GetInstance()->RegisterTriggerType(
                named_event_.c_str());

            StartTimer();
        }

        void OnStartedFinalizing(bool success)
        {
            if (!success)
                return;

            StartTimer();
        }

        void OnTriggerTimer()
        {
            BackgroundTracingManagerImpl::GetInstance()->TriggerNamedEvent(
                handle_, base::Bind(&TraceAtRandomIntervalsRule::OnStartedFinalizing, base::Unretained(this)));
        }

        void StartTimer()
        {
            int time_to_wait = base::RandInt(kReactiveTraceRandomStartTimeMin,
                kReactiveTraceRandomStartTimeMax);
            trigger_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(time_to_wait),
                base::Bind(&TraceAtRandomIntervalsRule::OnTriggerTimer,
                    base::Unretained(this)));
        }

        int GetTraceDelay() const override
        {
            return base::RandInt(timeout_min_, timeout_max_);
        }

        bool ShouldTriggerNamedEvent(const std::string& named_event) const override
        {
            return named_event == named_event_;
        }

        std::string GenerateUniqueName() const
        {
            static int ids = 0;
            char work_buffer[256];
            base::strings::SafeSNPrintf(work_buffer, sizeof(work_buffer), "%s_%d",
                kTraceAtRandomIntervalsEventName, ids++);
            return work_buffer;
        }

    private:
        std::string named_event_;
        base::OneShotTimer trigger_timer_;
        BackgroundTracingManagerImpl::TriggerHandle handle_;
        int timeout_min_;
        int timeout_max_;
    };

} // namespace

std::unique_ptr<BackgroundTracingRule>
BackgroundTracingRule::CreateRuleFromDict(const base::DictionaryValue* dict)
{
    DCHECK(dict);

    std::string type;
    if (!dict->GetString(kConfigRuleKey, &type))
        return nullptr;

    std::unique_ptr<BackgroundTracingRule> tracing_rule;
    if (type == kConfigRuleTypeMonitorNamed)
        tracing_rule = NamedTriggerRule::Create(dict);
    else if (type == kConfigRuleTypeMonitorHistogram)
        tracing_rule = HistogramRule::Create(dict);
    else if (type == kConfigRuleTypeTraceOnNavigationUntilTriggerOrFull) {
        tracing_rule = TraceForNSOrTriggerOrFullRule::Create(dict);
    } else if (type == kConfigRuleTypeTraceAtRandomIntervals) {
        tracing_rule = TraceAtRandomIntervalsRule::Create(dict);
    }

    if (tracing_rule)
        tracing_rule->Setup(dict);

    return tracing_rule;
}

} // namespace content
